//===--- PILGenConvert.cpp - Type Conversion Routines ---------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/pil/gen/PILGen.h"
#include "polarphp/pil/gen/ArgumentSource.h"
#include "polarphp/pil/gen/Conversion.h"
#include "polarphp/pil/gen/Initialization.h"
#include "polarphp/pil/gen/RValue.h"
#include "polarphp/pil/gen/Scope.h"
#include "polarphp/pil/gen/SwitchEnumBuilder.h"
#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/Decl.h"
#include "polarphp/ast/InterfaceConformance.h"
#include "polarphp/ast/SubstitutionMap.h"
#include "polarphp/ast/Types.h"
#include "polarphp/pil/lang/PILArgument.h"
#include "polarphp/pil/lang/TypeLowering.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/raw_ostream.h"

using namespace polar;
using namespace lowering;

// FIXME: With some changes to their callers, all of the below functions
// could be re-worked to use emitInjectEnum().
ManagedValue
PILGenFunction::emitInjectOptional(PILLocation loc,
                                   const TypeLowering &optTL,
                                   SGFContext ctxt,
                                   llvm::function_ref<ManagedValue(SGFContext)> generator) {
   PILType optTy = optTL.getLoweredType();
   PILType objectTy = optTy.getOptionalObjectType();
   assert(objectTy && "expected type was not optional");

   auto someDecl = getAstContext().getOptionalSomeDecl();

   // If the value is loadable, just emit and wrap.
   // TODO: honor +0 contexts?
   if (optTL.isLoadable() || !silConv.useLoweredAddresses()) {
      ManagedValue objectResult = generator(SGFContext());
      return B.createEnum(loc, objectResult, someDecl, optTy);
   }

   // Otherwise it's address-only; try to avoid spurious copies by
   // evaluating into the context.

   // Prepare a buffer for the object value.
   return B.bufferForExpr(
      loc, optTy.getObjectType(), optTL, ctxt,
      [&](PILValue optBuf) {
         auto objectBuf = B.createInitEnumDataAddr(loc, optBuf, someDecl, objectTy);

         // Evaluate the value in-place into that buffer.
         TemporaryInitialization init(objectBuf, CleanupHandle::invalid());
         ManagedValue objectResult = generator(SGFContext(&init));
         if (!objectResult.isInContext()) {
            objectResult.ensurePlusOne(*this, loc)
               .forwardInto(*this, loc, objectBuf);
         }

         // Finalize the outer optional buffer.
         B.createInjectEnumAddr(loc, optBuf, someDecl);
      });
}

void PILGenFunction::emitInjectOptionalValueInto(PILLocation loc,
                                                 ArgumentSource &&value,
                                                 PILValue dest,
                                                 const TypeLowering &optTL) {
   PILType optType = optTL.getLoweredType();
   assert(dest->getType() == optType.getAddressType());
   auto loweredPayloadTy = optType.getOptionalObjectType();
   assert(loweredPayloadTy);

   // Project out the payload area.
   auto someDecl = getAstContext().getOptionalSomeDecl();
   auto destPayload =
      B.createInitEnumDataAddr(loc, dest, someDecl,
                               loweredPayloadTy.getAddressType());

   // Emit the value into the payload area.
   TemporaryInitialization emitInto(destPayload, CleanupHandle::invalid());
   std::move(value).forwardInto(*this, &emitInto);

   // Inject the tag.
   B.createInjectEnumAddr(loc, dest, someDecl);
}

void PILGenFunction::emitInjectOptionalNothingInto(PILLocation loc,
                                                   PILValue dest,
                                                   const TypeLowering &optTL) {
   assert(optTL.getLoweredType().getOptionalObjectType());

   B.createInjectEnumAddr(loc, dest, getAstContext().getOptionalNoneDecl());
}

/// Return a value for an optional ".None" of the specified type. This only
/// works for loadable enum types.
PILValue PILGenFunction::getOptionalNoneValue(PILLocation loc,
                                              const TypeLowering &optTL) {
   assert((optTL.isLoadable() || !silConv.useLoweredAddresses()) &&
          "Address-only optionals cannot use this");
   assert(optTL.getLoweredType().getOptionalObjectType());

   return B.createEnum(loc, PILValue(), getAstContext().getOptionalNoneDecl(),
                       optTL.getLoweredType());
}

/// Return a value for an optional ".Some(x)" of the specified type. This only
/// works for loadable enum types.
ManagedValue PILGenFunction::
getOptionalSomeValue(PILLocation loc, ManagedValue value,
                     const TypeLowering &optTL) {
   assert((optTL.isLoadable() || !silConv.useLoweredAddresses()) &&
          "Address-only optionals cannot use this");
   PILType optType = optTL.getLoweredType();
   auto formalOptType = optType.getAstType();
   (void)formalOptType;

   assert(formalOptType.getOptionalObjectType());
   auto someDecl = getAstContext().getOptionalSomeDecl();

   return B.createEnum(loc, value, someDecl, optTL.getLoweredType());
}

auto PILGenFunction::emitSourceLocationArgs(SourceLoc sourceLoc,
                                            PILLocation emitLoc)
-> SourceLocArgs {
   auto &ctx = getAstContext();

   StringRef filename = "";
   unsigned line = 0;
   unsigned column = 0;
   if (sourceLoc.isValid()) {
      filename = ctx.SourceMgr.getDisplayNameForLoc(sourceLoc);
      std::tie(line, column) = ctx.SourceMgr.getLineAndColumn(sourceLoc);
   }

   bool isASCII = true;
   for (unsigned char c : filename) {
      if (c > 127) {
         isASCII = false;
         break;
      }
   }

   auto wordTy = PILType::getBuiltinWordType(ctx);
   auto i1Ty = PILType::getBuiltinIntegerType(1, ctx);

   SourceLocArgs result;
   PILValue literal = B.createStringLiteral(emitLoc, filename,
                                            StringLiteralInst::Encoding::UTF8);
   result.filenameStartPointer = ManagedValue::forUnmanaged(literal);
   // File length
   literal = B.createIntegerLiteral(emitLoc, wordTy, filename.size());
   result.filenameLength = ManagedValue::forUnmanaged(literal);
   // File is ascii
   literal = B.createIntegerLiteral(emitLoc, i1Ty, isASCII);
   result.filenameIsAscii = ManagedValue::forUnmanaged(literal);
   // Line
   literal = B.createIntegerLiteral(emitLoc, wordTy, line);
   result.line = ManagedValue::forUnmanaged(literal);
   // Column
   literal = B.createIntegerLiteral(emitLoc, wordTy, column);
   result.column = ManagedValue::forUnmanaged(literal);

   return result;
}

ManagedValue
PILGenFunction::emitPreconditionOptionalHasValue(PILLocation loc,
                                                 ManagedValue optional,
                                                 bool isImplicitUnwrap) {
   // Generate code to the optional is present, and if not, abort with a message
   // (provided by the stdlib).
   PILBasicBlock *contBB = createBasicBlock();
   PILBasicBlock *failBB = createBasicBlock();

   bool hadCleanup = optional.hasCleanup();
   bool hadLValue = optional.isLValue();

   auto someDecl = getAstContext().getOptionalSomeDecl();
   auto noneDecl = getAstContext().getOptionalNoneDecl();

   // If we have an object, make sure the object is at +1. All switch_enum of
   // objects is done at +1.
   if (optional.getType().isAddress()) {
      // We forward in the creation routine for
      // unchecked_take_enum_data_addr. switch_enum_addr is a +0 operation.
      B.createSwitchEnumAddr(loc, optional.getValue(),
         /*defaultDest*/ nullptr,
                             {{someDecl, contBB}, {noneDecl, failBB}});
   } else {
      optional = optional.ensurePlusOne(*this, loc);
      hadCleanup = true;
      hadLValue = false;
      B.createSwitchEnum(loc, optional.forward(*this),
         /*defaultDest*/ nullptr,
                         {{someDecl, contBB}, {noneDecl, failBB}});
   }
   B.emitBlock(failBB);

   // Call the standard library implementation of _diagnoseUnexpectedNilOptional.
   if (auto diagnoseFailure =
      getAstContext().getDiagnoseUnexpectedNilOptional()) {
      auto args = emitSourceLocationArgs(loc.getSourceLoc(), loc);

      auto i1Ty = PILType::getBuiltinIntegerType(1, getAstContext());
      auto isImplicitUnwrapLiteral =
         B.createIntegerLiteral(loc, i1Ty, isImplicitUnwrap);
      auto isImplicitUnwrapValue =
         ManagedValue::forUnmanaged(isImplicitUnwrapLiteral);

      emitApplyOfLibraryIntrinsic(loc, diagnoseFailure, SubstitutionMap(),
                                  {
                                     args.filenameStartPointer,
                                     args.filenameLength,
                                     args.filenameIsAscii,
                                     args.line,
                                     isImplicitUnwrapValue
                                  },
                                  SGFContext());
   }

   B.createUnreachable(ArtificialUnreachableLocation());
   B.clearInsertionPoint();
   B.emitBlock(contBB);

   ManagedValue result;
   PILType payloadType = optional.getType().getOptionalObjectType();

   if (payloadType.isObject()) {
      result = B.createOwnedPhiArgument(payloadType);
   } else {
      result =
         B.createUncheckedTakeEnumDataAddr(loc, optional, someDecl, payloadType);
   }

   if (hadCleanup) {
      return result;
   }

   if (hadLValue) {
      return ManagedValue::forLValue(result.forward(*this));
   }

   return ManagedValue::forUnmanaged(result.forward(*this));
}

PILValue PILGenFunction::emitDoesOptionalHaveValue(PILLocation loc,
                                                   PILValue addrOrValue) {
   auto boolTy = PILType::getBuiltinIntegerType(1, getAstContext());
   PILValue yes = B.createIntegerLiteral(loc, boolTy, 1);
   PILValue no = B.createIntegerLiteral(loc, boolTy, 0);
   auto someDecl = getAstContext().getOptionalSomeDecl();

   if (addrOrValue->getType().isAddress())
      return B.createSelectEnumAddr(loc, addrOrValue, boolTy, no,
                                    std::make_pair(someDecl, yes));
   return B.createSelectEnum(loc, addrOrValue, boolTy, no,
                             std::make_pair(someDecl, yes));
}

ManagedValue PILGenFunction::emitCheckedGetOptionalValueFrom(PILLocation loc,
                                                             ManagedValue src,
                                                             bool isImplicitUnwrap,
                                                             const TypeLowering &optTL,
                                                             SGFContext C) {
   // TODO: Make this take optTL.
   return emitPreconditionOptionalHasValue(loc, src, isImplicitUnwrap);
}

ManagedValue PILGenFunction::emitUncheckedGetOptionalValueFrom(
   PILLocation loc, ManagedValue addrOrValue, const TypeLowering &optTL,
   SGFContext C) {
   PILType origPayloadTy = addrOrValue.getType().getOptionalObjectType();

   auto someDecl = getAstContext().getOptionalSomeDecl();

   // Take the payload from the optional.
   if (!addrOrValue.getType().isAddress()) {
      return B.createUncheckedEnumData(loc, addrOrValue, someDecl);
   }

   // Cheat a bit in the +0 case--UncheckedTakeEnumData will never actually
   // invalidate an Optional enum value. This is specific to optionals.
   ManagedValue payload = B.createUncheckedTakeEnumDataAddr(
      loc, addrOrValue, someDecl, origPayloadTy);
   if (!optTL.isLoadable())
      return payload;

   // If we do not have a cleanup on our address, use a load_borrow.
   if (!payload.hasCleanup()) {
      return B.createLoadBorrow(loc, payload);
   }

   // Otherwise, perform a load take.
   return B.createLoadTake(loc, payload);
}

ManagedValue
PILGenFunction::emitOptionalSome(PILLocation loc, PILType optTy,
                                 ValueProducerRef produceValue,
                                 SGFContext C) {
   // If the conversion is a bridging conversion from an optional type,
   // do a bridging conversion from the non-optional type instead.
   // TODO: should this be a general thing for all conversions?
   if (auto optInit = C.getAsConversion()) {
      const auto &optConversion = optInit->getConversion();
      if (optConversion.isBridging()) {
         auto sourceValueType =
            optConversion.getBridgingSourceType().getOptionalObjectType();
         assert(sourceValueType);
         if (auto valueConversion =
            optConversion.adjustForInitialOptionalConversions(sourceValueType)){
            return optInit->emitWithAdjustedConversion(*this, loc, *valueConversion,
                                                       produceValue);
         }
      }
   }

   auto &optTL = getTypeLowering(optTy);

   // If the type is loadable or we're not lowering address-only types
   // in PILGen, use a simple scalar pattern.
   if (!silConv.useLoweredAddresses() || optTL.isLoadable()) {
      auto value = produceValue(*this, loc, SGFContext());
      return getOptionalSomeValue(loc, value, optTL);
   }

   // Otherwise, emit into memory, preferably into an address from
   // the context.

   // Get an address to emit into.
   PILValue optAddr = getBufferForExprResult(loc, optTy, C);

   auto someDecl = getAstContext().getOptionalSomeDecl();

   auto valueTy = optTy.getOptionalObjectType();
   auto &valueTL = getTypeLowering(valueTy);

   // Project the value buffer within the address.
   PILValue valueAddr =
      B.createInitEnumDataAddr(loc, optAddr, someDecl,
                               valueTy.getAddressType());

   // Emit into the value buffer.
   auto valueInit = useBufferAsTemporary(valueAddr, valueTL);
   ManagedValue value = produceValue(*this, loc, SGFContext(valueInit.get()));
   if (!value.isInContext()) {
      valueInit->copyOrInitValueInto(*this, loc, value, /*isInit*/ true);
      valueInit->finishInitialization(*this);
   }

   // Kill the cleanup on the value.
   valueInit->getManagedAddress().forward(*this);

   // Finish the optional.
   B.createInjectEnumAddr(loc, optAddr, someDecl);

   return manageBufferForExprResult(optAddr, optTL, C);
}

/// Emit an optional-to-optional transformation.
ManagedValue
PILGenFunction::emitOptionalToOptional(PILLocation loc,
                                       ManagedValue input,
                                       PILType resultTy,
                                       ValueTransformRef transformValue,
                                       SGFContext C) {
   auto &Ctx = getAstContext();

   // If the input is known to be 'none' just emit a 'none' value of the right
   // result type right away.
   auto &resultTL = getTypeLowering(resultTy);

   if (auto *EI = dyn_cast<EnumInst>(input.getValue())) {
      if (EI->getElement() == Ctx.getOptionalNoneDecl()) {
         if (!(resultTL.isAddressOnly() && silConv.useLoweredAddresses())) {
            PILValue none = B.createEnum(loc, PILValue(), EI->getElement(),
                                         resultTy);
            return emitManagedRValueWithCleanup(none);
         }
      }
   }

   // Otherwise perform a dispatch.
   auto contBB = createBasicBlock();
   auto isNotPresentBB = createBasicBlock();
   auto isPresentBB = createBasicBlock();

   // All conversions happen at +1.
   input = input.ensurePlusOne(*this, loc);

   SwitchEnumBuilder SEBuilder(B, loc, input);
   PILType noOptResultTy = resultTy.getOptionalObjectType();
   assert(noOptResultTy);

   // Create a temporary for the output optional.
   //
   // If the result is address-only, we need to return something in memory,
   // otherwise the result is the BBArgument in the merge point.
   // TODO: use the SGFContext passed in.
   ManagedValue finalResult;
   if (resultTL.isAddressOnly() && silConv.useLoweredAddresses()) {
      finalResult = emitManagedBufferWithCleanup(
         emitTemporaryAllocation(loc, resultTy), resultTL);
   } else {
      PILGenSavedInsertionPoint IP(*this, contBB);
      finalResult = B.createOwnedPhiArgument(resultTL.getLoweredType());
   }

   SEBuilder.addOptionalSomeCase(
      isPresentBB, contBB, [&](ManagedValue input, SwitchCaseFullExpr &&scope) {
         // If we have an address only type, we want to match the old behavior of
         // transforming the underlying type instead of the optional type. This
         // ensures that we use the more efficient non-generic code paths when
         // possible.
         if (getTypeLowering(input.getType()).isAddressOnly() &&
             silConv.useLoweredAddresses()) {
            auto *someDecl = Ctx.getOptionalSomeDecl();
            input = B.createUncheckedTakeEnumDataAddr(
               loc, input, someDecl, input.getType().getOptionalObjectType());
         }

         ManagedValue result = transformValue(*this, loc, input, noOptResultTy,
                                              SGFContext());

         if (!(resultTL.isAddressOnly() && silConv.useLoweredAddresses())) {
            PILValue some = B.createOptionalSome(loc, result).forward(*this);
            return scope.exitAndBranch(loc, some);
         }

         RValue R(*this, loc, noOptResultTy.getAstType(), result);
         ArgumentSource resultValueRV(loc, std::move(R));
         emitInjectOptionalValueInto(loc, std::move(resultValueRV),
                                     finalResult.getValue(), resultTL);
         return scope.exitAndBranch(loc);
      });

   SEBuilder.addOptionalNoneCase(
      isNotPresentBB, contBB,
      [&](ManagedValue input, SwitchCaseFullExpr &&scope) {
         if (!(resultTL.isAddressOnly() && silConv.useLoweredAddresses())) {
            PILValue none =
               B.createManagedOptionalNone(loc, resultTy).forward(*this);
            return scope.exitAndBranch(loc, none);
         }

         emitInjectOptionalNothingInto(loc, finalResult.getValue(), resultTL);
         return scope.exitAndBranch(loc);
      });

   std::move(SEBuilder).emit();

   B.emitBlock(contBB);
   return finalResult;
}

PILGenFunction::OpaqueValueRAII::~OpaqueValueRAII() {
   auto entry = Self.OpaqueValues.find(OpaqueValue);
   assert(entry != Self.OpaqueValues.end());
   Self.OpaqueValues.erase(entry);
}

RValue
PILGenFunction::emitPointerToPointer(PILLocation loc,
                                     ManagedValue input,
                                     CanType inputType,
                                     CanType outputType,
                                     SGFContext C) {
   auto converter = getAstContext().getConvertPointerToPointerArgument();

   auto origValue = input;
   if (silConv.useLoweredAddresses()) {
      // The generic function currently always requires indirection, but pointers
      // are always loadable.
      auto origBuf = emitTemporaryAllocation(loc, input.getType());
      B.emitStoreValueOperation(loc, input.forward(*this), origBuf,
                                StoreOwnershipQualifier::Init);
      origValue = emitManagedBufferWithCleanup(origBuf);
   }
   // Invoke the conversion intrinsic to convert to the destination type.
   auto *M = SGM.M.getTypePHPModule();
   auto *proto = getPointerInterface();
   auto firstSubMap = inputType->getContextSubstitutionMap(M, proto);
   auto secondSubMap = outputType->getContextSubstitutionMap(M, proto);

   auto genericSig = converter->getGenericSignature();
   auto subMap =
      SubstitutionMap::combineSubstitutionMaps(firstSubMap,
                                               secondSubMap,
                                               CombineSubstitutionMaps::AtIndex,
                                               1, 0,
                                               genericSig);

   return emitApplyOfLibraryIntrinsic(loc, converter, subMap, origValue, C);
}


namespace {

/// This is an initialization for an address-only existential in memory.
class ExistentialInitialization final : public SingleBufferInitialization {
   PILValue existential;
   CanType concreteFormalType;
   ArrayRef<InterfaceConformanceRef> conformances;
   ExistentialRepresentation repr;

   // Initialized lazily when the address for initialization is demanded.
   PILValue concreteBuffer;
   CleanupHandle deinitExistentialCleanup;
public:
   /// \param existential The existential container
   /// \param concreteFormalType Unlowered AST type of value
   /// \param conformances Conformances for concrete type to existential's
   ///        protocols
   ExistentialInitialization(PILGenFunction &SGF,
                             PILValue existential,
                             CanType concreteFormalType,
                             ArrayRef<InterfaceConformanceRef> conformances,
                             ExistentialRepresentation repr)
      : existential(existential),
        concreteFormalType(concreteFormalType),
        conformances(conformances),
        repr(repr)
   {
      assert(existential->getType().isAddress());

      // Create a cleanup to deallocate an allocated but uninitialized concrete
      // type buffer.
      // It won't be activated until that buffer is formed later, though.
      deinitExistentialCleanup =
         SGF.enterDeinitExistentialCleanup(CleanupState::Dormant,
                                           existential, concreteFormalType, repr);

   }

   PILValue getAddressForInPlaceInitialization(PILGenFunction &SGF,
                                               PILLocation loc) override {
      // Create the buffer when needed, because in some cases the type may
      // be the opened type from another existential that hasn't been opened
      // at the point the existential destination was formed.
      assert(!concreteBuffer && "concrete buffer already formed?!");

      auto concreteLoweredType =
         SGF.getLoweredType(AbstractionPattern::getOpaque(), concreteFormalType);

      switch (repr) {
         case ExistentialRepresentation::Opaque: {
            concreteBuffer = SGF.B.createInitExistentialAddr(loc, existential,
                                                             concreteFormalType,
                                                             concreteLoweredType.getAddressType(),
                                                             conformances);
            break;
         }
         case ExistentialRepresentation::Boxed: {
            auto box = SGF.B.createAllocExistentialBox(loc,
                                                       existential->getType().getObjectType(),
                                                       concreteFormalType,
                                                       conformances);
            concreteBuffer = SGF.B.createProjectExistentialBox(loc,
                                                               concreteLoweredType.getAddressType(),
                                                               box);
            SGF.B.createStore(loc, box, existential,
                              StoreOwnershipQualifier::Init);
            break;
         }
         case ExistentialRepresentation::Class:
         case ExistentialRepresentation::Metatype:
         case ExistentialRepresentation::None:
            llvm_unreachable("not supported");
      }

      // Activate the cleanup to deallocate the buffer we just allocated, should
      SGF.Cleanups.setCleanupState(deinitExistentialCleanup,
                                   CleanupState::Active);

      return concreteBuffer;
   }

   bool isInPlaceInitializationOfGlobal() const override {
      return existential && isa<GlobalAddrInst>(existential);
   }

   void finishInitialization(PILGenFunction &SGF) override {
      SingleBufferInitialization::finishInitialization(SGF);
      // We've fully initialized the existential by this point, so we can
      // retire the partial cleanup.
      SGF.Cleanups.setCleanupState(deinitExistentialCleanup,
                                   CleanupState::Dead);
   }
};

} // end anonymous namespace

ManagedValue PILGenFunction::emitExistentialErasure(
   PILLocation loc,
   CanType concreteFormalType,
   const TypeLowering &concreteTL,
   const TypeLowering &existentialTL,
   ArrayRef<InterfaceConformanceRef> conformances,
   SGFContext C,
   llvm::function_ref<ManagedValue (SGFContext)> F,
   bool allowEmbeddedNSError) {
   // Mark the needed conformances as used.
   for (auto conformance : conformances)
      SGM.useConformance(conformance);

   // If we're erasing to the 'Error' type, we might be able to get an NSError
   // representation more efficiently.
   auto &ctx = getAstContext();
   // @todo
//   if (ctx.LangOpts.EnableObjCInterop && conformances.size() == 1 &&
//       conformances[0].getRequirement() == ctx.getErrorDecl() &&
//       ctx.getNSErrorDecl()) {
//      // If the concrete type is NSError or a subclass thereof, just erase it
//      // directly.
//      auto nsErrorType = ctx.getNSErrorType()->getCanonicalType();
//      if (nsErrorType->isExactSuperclassOf(concreteFormalType)) {
//         ManagedValue nsError =  F(SGFContext());
//         if (nsErrorType != concreteFormalType) {
//            nsError = B.createUpcast(loc, nsError, getLoweredType(nsErrorType));
//         }
//         return emitBridgedToNativeError(loc, nsError);
//      }
//
//      // If the concrete type is known to conform to _BridgedStoredNSError,
//      // call the _nsError witness getter to extract the NSError directly,
//      // then just erase the NSError.
//      auto storedNSErrorConformance =
//         SGM.getConformanceToBridgedStoredNSError(loc, concreteFormalType);
//      if (storedNSErrorConformance) {
//         auto nsErrorVar = SGM.getNSErrorRequirement(loc);
//         if (!nsErrorVar) return emitUndef(existentialTL.getLoweredType());
//
//         SubstitutionMap nsErrorVarSubstitutions;
//
//         // Devirtualize.  Maybe this should be done implicitly by
//         // emitPropertyLValue?
//         if (storedNSErrorConformance.isConcrete()) {
//            if (auto normal = dyn_cast<NormalInterfaceConformance>(
//               storedNSErrorConformance.getConcrete())) {
//               if (auto witnessVar = normal->getWitness(nsErrorVar)) {
//                  nsErrorVar = cast<VarDecl>(witnessVar.getDecl());
//                  nsErrorVarSubstitutions = witnessVar.getSubstitutions();
//               }
//            }
//         }
//
//         ManagedValue nativeError = F(SGFContext());
//
//         FormalEvaluationScope writebackScope(*this);
//         ManagedValue nsError =
//            emitRValueForStorageLoad(
//               loc, nativeError, concreteFormalType,
//               /*super*/ false, nsErrorVar, PreparedArguments(),
//               nsErrorVarSubstitutions,
//               AccessSemantics::Ordinary, nsErrorType, SGFContext())
//               .getAsSingleValue(*this, loc);
//
//         return emitBridgedToNativeError(loc, nsError);
//      }
//
//      // Otherwise, if it's an archetype, try calling the _getEmbeddedNSError()
//      // witness to try to dig out the embedded NSError.  But don't do this
//      // when we're being called recursively.
//      if (isa<ArchetypeType>(concreteFormalType) && allowEmbeddedNSError) {
//         auto contBB = createBasicBlock();
//         auto isNotPresentBB = createBasicBlock();
//         auto isPresentBB = createBasicBlock();
//
//         // Call swift_stdlib_getErrorEmbeddedNSError to attempt to extract an
//         // NSError from the value.
//         auto getEmbeddedNSErrorFn = SGM.getGetErrorEmbeddedNSError(loc);
//         if (!getEmbeddedNSErrorFn)
//            return emitUndef(existentialTL.getLoweredType());
//
//         auto getEmbeddedNSErrorSubstitutions =
//            SubstitutionMap::getInterfaceSubstitutions(ctx.getErrorDecl(),
//                                                      concreteFormalType,
//                                                      conformances[0]);
//
//         ManagedValue concreteValue = F(SGFContext());
//         ManagedValue potentialNSError =
//            emitApplyOfLibraryIntrinsic(loc,
//                                        getEmbeddedNSErrorFn,
//                                        getEmbeddedNSErrorSubstitutions,
//                                        { concreteValue.copy(*this, loc) },
//                                        SGFContext())
//               .getAsSingleValue(*this, loc);
//
//         // We're going to consume 'concreteValue' in exactly one branch,
//         // so kill its cleanup now and recreate it on both branches.
//         (void) concreteValue.forward(*this);
//
//         // Check whether we got an NSError back.
//         std::pair<EnumElementDecl*, PILBasicBlock*> cases[] = {
//            { ctx.getOptionalSomeDecl(), isPresentBB },
//            { ctx.getOptionalNoneDecl(), isNotPresentBB }
//         };
//         B.createSwitchEnum(loc, potentialNSError.forward(*this),
//            /*default*/ nullptr, cases);
//
//         // If we did get an NSError, emit the existential erasure from that
//         // NSError.
//         B.emitBlock(isPresentBB);
//         PILValue branchArg;
//         {
//            // Don't allow cleanups to escape the conditional block.
//            FullExpr presentScope(Cleanups, CleanupLocation::get(loc));
//            enterDestroyCleanup(concreteValue.getValue());
//
//            // Receive the error value.  It's typed as an 'AnyObject' for
//            // layering reasons, so perform an unchecked cast down to NSError.
//            PILType anyObjectTy =
//               potentialNSError.getType().getOptionalObjectType();
//            ManagedValue nsError = B.createOwnedPhiArgument(anyObjectTy);
//            nsError = B.createUncheckedRefCast(loc, nsError,
//                                               getLoweredType(nsErrorType));
//
//            branchArg = emitBridgedToNativeError(loc, nsError).forward(*this);
//         }
//         B.createBranch(loc, contBB, branchArg);
//
//         // If we did not get an NSError, just directly emit the existential.
//         // Since this is a recursive call, make sure we don't end up in this
//         // path again.
//         B.emitBlock(isNotPresentBB);
//         {
//            FullExpr presentScope(Cleanups, CleanupLocation::get(loc));
//            concreteValue = emitManagedRValueWithCleanup(concreteValue.getValue());
//            branchArg = emitExistentialErasure(loc, concreteFormalType, concreteTL,
//                                               existentialTL, conformances,
//                                               SGFContext(),
//                                               [&](SGFContext C) {
//                                                  return concreteValue;
//                                               },
//               /*allowEmbeddedNSError=*/false)
//               .forward(*this);
//         }
//         B.createBranch(loc, contBB, branchArg);
//
//         // Continue.
//         B.emitBlock(contBB);
//
//         PILValue existentialResult = contBB->createPhiArgument(
//            existentialTL.getLoweredType(), ValueOwnershipKind::Owned);
//         return emitManagedRValueWithCleanup(existentialResult, existentialTL);
//      }
//   }

   switch (existentialTL.getLoweredType().getObjectType()
      .getPreferredExistentialRepresentation(concreteFormalType)) {
      case ExistentialRepresentation::None:
         llvm_unreachable("not an existential type");
      case ExistentialRepresentation::Metatype: {
         assert(existentialTL.isLoadable());

         PILValue metatype = F(SGFContext()).getUnmanagedValue();
         assert(metatype->getType().castTo<AnyMetatypeType>()->getRepresentation()
                == MetatypeRepresentation::Thick);

         auto upcast =
            B.createInitExistentialMetatype(loc, metatype,
                                            existentialTL.getLoweredType(),
                                            conformances);
         return ManagedValue::forUnmanaged(upcast);
      }
      case ExistentialRepresentation::Class: {
         assert(existentialTL.isLoadable());

         ManagedValue sub = F(SGFContext());
         assert(concreteFormalType->isBridgeableObjectType());
         return B.createInitExistentialRef(loc, existentialTL.getLoweredType(),
                                           concreteFormalType, sub, conformances);
      }
      case ExistentialRepresentation::Boxed: {
         // We defer allocation of the box to when the address is demanded.
         // Create a stack slot to hold the box once it's allocated.
         PILValue boxValue;
         auto buf = B.bufferForExpr(
            loc, existentialTL.getLoweredType(), existentialTL, C,
            [&](PILValue existential) {
               // Initialize the existential in-place.
               ExistentialInitialization init(*this, existential,
                                              concreteFormalType,
                                              conformances,
                                              ExistentialRepresentation::Boxed);
               ManagedValue mv = F(SGFContext(&init));
               if (!mv.isInContext()) {
                  init.copyOrInitValueInto(*this, loc, mv.ensurePlusOne(*this, loc),
                     /*init*/ true);
                  init.finishInitialization(*this);
               }
            });

         if (buf.isInContext()) {
            return buf;
         }

         auto value = B.createLoad(loc, buf.forward(*this),
                                   LoadOwnershipQualifier::Take);
         return emitManagedRValueWithCleanup(value);
      }
      case ExistentialRepresentation::Opaque: {

         // If the concrete value is a pseudogeneric archetype, first erase it to
         // its upper bound.
         auto anyObjectTy = getAstContext().getAnyObjectType();
         auto eraseToAnyObject =
            [&, concreteFormalType, F](SGFContext C) -> ManagedValue {
               auto concreteValue = F(SGFContext());
               assert(concreteFormalType->isBridgeableObjectType());
               return B.createInitExistentialRef(
                  loc, PILType::getPrimitiveObjectType(anyObjectTy), concreteFormalType,
                  concreteValue, {});
            };

         auto concreteTLPtr = &concreteTL;
         if (this->F.getLoweredFunctionType()->isPseudogeneric()) {
            if (anyObjectTy && concreteFormalType->is<ArchetypeType>()) {
               concreteFormalType = anyObjectTy;
               concreteTLPtr = &getTypeLowering(anyObjectTy);
               F = eraseToAnyObject;
            }
         }

         if (!silConv.useLoweredAddresses()) {
            // We should never create new buffers just for init_existential under
            // opaque values mode: This is a case of an opaque value that we can
            // "treat" as a by-value one
            ManagedValue sub = F(SGFContext());
            return B.createInitExistentialValue(
               loc, existentialTL.getLoweredType(), concreteFormalType,
               sub, conformances);
         }

         // Allocate the existential.
         return B.bufferForExpr(
            loc, existentialTL.getLoweredType(), existentialTL, C,
            [&](PILValue existential) {
               // Initialize the existential in-place.
               ExistentialInitialization init(*this, existential,
                                              concreteFormalType,
                                              conformances,
                                              ExistentialRepresentation::Opaque);
               ManagedValue mv = F(SGFContext(&init));
               if (!mv.isInContext()) {
                  init.copyOrInitValueInto(*this, loc, mv.ensurePlusOne(*this, loc),
                     /*init*/ true);
                  init.finishInitialization(*this);
               }
            });
      }
   }

   llvm_unreachable("Unhandled ExistentialRepresentation in switch.");
}

ManagedValue PILGenFunction::emitClassMetatypeToObject(PILLocation loc,
                                                       ManagedValue v,
                                                       PILType resultTy) {
   PILValue value = v.getUnmanagedValue();

   // Convert the metatype to objc representation.
   auto metatypeTy = value->getType().castTo<MetatypeType>();
   auto objcMetatypeTy = CanMetatypeType::get(metatypeTy.getInstanceType(),
                                              MetatypeRepresentation::ObjC);
   // @todo
//   value = B.createThickToObjCMetatype(loc, value,
//                                       PILType::getPrimitiveObjectType(objcMetatypeTy));
//
//   // Convert to an object reference.
//   value = B.createObjCMetatypeToObject(loc, value, resultTy);
   return emitManagedRValueWithCleanup(value);
}

ManagedValue PILGenFunction::emitExistentialMetatypeToObject(PILLocation loc,
                                                             ManagedValue v,
                                                             PILType resultTy) {
   PILValue value = v.getUnmanagedValue();

   // Convert the metatype to objc representation.
   auto metatypeTy = value->getType().castTo<ExistentialMetatypeType>();
   auto objcMetatypeTy = CanExistentialMetatypeType::get(
      metatypeTy.getInstanceType(),
      MetatypeRepresentation::ObjC);
   // @todo
//   value = B.createThickToObjCMetatype(loc, value,
//                                       PILType::getPrimitiveObjectType(objcMetatypeTy));
//
//   // Convert to an object reference.
//   value = B.createObjCExistentialMetatypeToObject(loc, value, resultTy);

   return emitManagedRValueWithCleanup(value);
}
// @todo
//ManagedValue PILGenFunction::emitInterfaceMetatypeToObject(PILLocation loc,
//                                                          CanType inputTy,
//                                                          PILType resultTy) {
//   InterfaceDecl *protocol = inputTy->castTo<MetatypeType>()
//      ->getInstanceType()->castTo<InterfaceType>()->getDecl();
//
////   PILValue value = B.createObjCInterface(loc, protocol, resultTy);
//
//   // Interface objects, despite being global objects, inherit default reference
//   // counting semantics from NSObject, so we need to retain the protocol
//   // reference when we use it to prevent it being released and attempting to
//   // deallocate itself. It doesn't matter if we ever actually clean up that
//   // retain though.
////   value = B.createCopyValue(loc, value);
////   return emitManagedRValueWithCleanup(value);
//}

ManagedValue
PILGenFunction::emitOpenExistential(
   PILLocation loc,
   ManagedValue existentialValue,
   PILType loweredOpenedType,
   AccessKind accessKind) {
   assert(isInFormalEvaluationScope());

   PILType existentialType = existentialValue.getType();
   switch (existentialType.getPreferredExistentialRepresentation()) {
      case ExistentialRepresentation::Opaque: {
         // With CoW existentials we can't consume the boxed value inside of
         // the existential. (We could only do so after a uniqueness check on
         // the box holding the value).
         if (existentialType.isAddress()) {
            OpenedExistentialAccess allowedAccess =
               getOpenedExistentialAccessFor(accessKind);
            if (!loweredOpenedType.isAddress()) {
               assert(!silConv.useLoweredAddresses() &&
                      "Non-address loweredOpenedType is only allowed under opaque "
                      "value mode");
               loweredOpenedType = loweredOpenedType.getAddressType();
            }
            PILValue archetypeValue =
               B.createOpenExistentialAddr(loc, existentialValue.getValue(),
                                           loweredOpenedType, allowedAccess);
            return ManagedValue::forUnmanaged(archetypeValue);
         } else {
            // borrow the existential and return an unmanaged opened value.
            return B.createOpenExistentialValue(
               loc, existentialValue, loweredOpenedType);
         }
      }
      case ExistentialRepresentation::Metatype:
         assert(existentialType.isObject());
         return B.createOpenExistentialMetatype(
            loc, existentialValue, loweredOpenedType);
      case ExistentialRepresentation::Class:
         assert(existentialType.isObject());
         return B.createOpenExistentialRef(loc, existentialValue, loweredOpenedType);
      case ExistentialRepresentation::Boxed:
         if (existentialType.isAddress()) {
            existentialValue = emitLoad(loc, existentialValue.getValue(),
                                        getTypeLowering(existentialType),
                                        SGFContext::AllowGuaranteedPlusZero,
                                        IsNotTake);
         }

         existentialType = existentialValue.getType();
         assert(existentialType.isObject());
         if (loweredOpenedType.isAddress()) {
            return ManagedValue::forUnmanaged(
               B.createOpenExistentialBox(loc, existentialValue.getValue(),
                                          loweredOpenedType));
         } else {
            assert(!silConv.useLoweredAddresses());
            return B.createOpenExistentialBoxValue(
               loc, existentialValue, loweredOpenedType);
         }
      case ExistentialRepresentation::None:
         llvm_unreachable("not existential");
   }
   llvm_unreachable("covered switch");
}

ManagedValue PILGenFunction::manageOpaqueValue(ManagedValue value,
                                               PILLocation loc,
                                               SGFContext C) {
   // If the opaque value is consumable, we can just return the
   // value with a cleanup. There is no need to retain it separately.
   if (value.isPlusOne(*this))
      return value;

   // If the context wants a +0 value, guaranteed or immediate, we can
   // give it to them, because OpenExistential emission guarantees the
   // value.
   if (C.isGuaranteedPlusZeroOk())
      return value;

   // If the context has an initialization a buffer, copy there instead
   // of making a temporary allocation.
   if (auto I = C.getEmitInto()) {
      I->copyOrInitValueInto(*this, loc, value, /*init*/ false);
      I->finishInitialization(*this);
      return ManagedValue::forInContext();
   }

   // Otherwise, copy the value into a temporary.
   return value.copyUnmanaged(*this, loc);
}

ManagedValue PILGenFunction::emitConvertedRValue(Expr *E,
                                                 const Conversion &conversion,
                                                 SGFContext C) {
   return emitConvertedRValue(E, conversion, C,
                              [&](PILGenFunction &SGF, PILLocation loc, SGFContext C) {
                                 return emitRValueAsSingleValue(E, C);
                              });
}

ManagedValue PILGenFunction::emitConvertedRValue(PILLocation loc,
                                                 const Conversion &conversion,
                                                 SGFContext C,
                                                 ValueProducerRef produceValue){
   // If we're emitting into a converting context, check whether we can
   // peephole the conversions together.
   if (auto outerConversion = C.getAsConversion()) {
      if (outerConversion->tryPeephole(*this, loc, conversion, produceValue)) {
         outerConversion->finishInitialization(*this);
         return ManagedValue::forInContext();
      }
   }

   // Otherwise, set up a reabstracting context and try to emit into that.
   ConvertingInitialization init(conversion, C);
   auto result = produceValue(*this, loc, SGFContext(&init));
   auto finishedResult = init.finishEmission(*this, loc, result);
   return finishedResult;
}

ManagedValue
ConvertingInitialization::finishEmission(PILGenFunction &SGF,
                                         PILLocation loc,
                                         ManagedValue formalResult) {
   switch (getState()) {
      case Uninitialized:
         assert(!formalResult.isInContext());
         State = Extracted;
         return TheConversion.emit(SGF, loc, formalResult, FinalContext);

      case Initialized:
         llvm_unreachable("initialization never finished");

      case Finished:
         assert(formalResult.isInContext());
         assert(!Value.isInContext() || FinalContext.getEmitInto());
         State = Extracted;
         return Value;

      case Extracted:
         llvm_unreachable("value already extracted");
   }
   llvm_unreachable("bad state");
}

bool ConvertingInitialization::tryPeephole(PILGenFunction &SGF,
                                           PILLocation loc,
                                           ManagedValue origValue,
                                           Conversion innerConversion) {
   return tryPeephole(SGF, loc, innerConversion,
                      [&](PILGenFunction &SGF, PILLocation loc, SGFContext C) {
                         return origValue;
                      });
}

bool ConvertingInitialization::tryPeephole(PILGenFunction &SGF,
                                           Expr *E,
                                           Conversion innerConversion) {
   return tryPeephole(SGF, E, innerConversion,
                      [&](PILGenFunction &SGF, PILLocation loc, SGFContext C) {
                         return SGF.emitRValueAsSingleValue(E, C);
                      });
}

bool ConvertingInitialization::tryPeephole(PILGenFunction &SGF, PILLocation loc,
                                           Conversion innerConversion,
                                           ValueProducerRef produceValue) {
   const auto &outerConversion = getConversion();
   auto hint = canPeepholeConversions(SGF, outerConversion, innerConversion);
   if (!hint)
      return false;

   ManagedValue value = emitPeepholedConversions(SGF, loc, outerConversion,
                                                 innerConversion, *hint,
                                                 FinalContext, produceValue);
   setConvertedValue(value);
   return true;
}

void ConvertingInitialization::copyOrInitValueInto(PILGenFunction &SGF,
                                                   PILLocation loc,
                                                   ManagedValue formalValue,
                                                   bool isInit) {
   assert(getState() == Uninitialized && "already have saved value?");

   // TODO: take advantage of borrowed inputs?
   if (!isInit) formalValue = formalValue.copy(SGF, loc);
   State = Initialized;
   Value = TheConversion.emit(SGF, loc, formalValue, FinalContext);
}

ManagedValue
ConvertingInitialization::emitWithAdjustedConversion(PILGenFunction &SGF,
                                                     PILLocation loc,
                                                     Conversion adjustedConversion,
                                                     ValueProducerRef produceValue) {
   ConvertingInitialization init(adjustedConversion, getFinalContext());
   auto result = produceValue(SGF, loc, SGFContext(&init));
   result = init.finishEmission(SGF, loc, result);
   setConvertedValue(result);
   finishInitialization(SGF);
   return ManagedValue::forInContext();
}

ManagedValue Conversion::emit(PILGenFunction &SGF, PILLocation loc,
                              ManagedValue value, SGFContext C) const {
   switch (getKind()) {
      case AnyErasure:
         return SGF.emitTransformedValue(loc, value, getBridgingSourceType(),
                                         getBridgingResultType(), C);

      case BridgeToObjC:
         return SGF.emitNativeToBridgedValue(loc, value,
                                             getBridgingSourceType(),
                                             getBridgingResultType(),
                                             getBridgingLoweredResultType(), C);

      case ForceAndBridgeToObjC: {
         auto &tl = SGF.getTypeLowering(value.getType());
         auto sourceValueType = getBridgingSourceType().getOptionalObjectType();
         value = SGF.emitCheckedGetOptionalValueFrom(loc, value,
            /*isImplicitUnwrap*/ true,
                                                     tl, SGFContext());
         return SGF.emitNativeToBridgedValue(loc, value, sourceValueType,
                                             getBridgingResultType(),
                                             getBridgingLoweredResultType(), C);
      }

      case BridgeFromObjC:
         return SGF.emitBridgedToNativeValue(loc, value,
                                             getBridgingSourceType(),
                                             getBridgingResultType(),
                                             getBridgingLoweredResultType(), C);

      case BridgeResultFromObjC:
         return SGF.emitBridgedToNativeValue(loc, value,
                                             getBridgingSourceType(),
                                             getBridgingResultType(),
                                             getBridgingLoweredResultType(), C,
            /*isResult*/ true);

      case SubstToOrig:
         return SGF.emitSubstToOrigValue(loc, value,
                                         getReabstractionOrigType(),
                                         getReabstractionSubstType(), C);

      case OrigToSubst:
         return SGF.emitOrigToSubstValue(loc, value,
                                         getReabstractionOrigType(),
                                         getReabstractionSubstType(), C);
   }
   llvm_unreachable("bad kind");
}

Optional<Conversion>
Conversion::adjustForInitialOptionalConversions(CanType newSourceType) const {
   switch (getKind()) {
      case SubstToOrig:
      case OrigToSubst:
         // TODO: handle reabstraction conversions here, too.
         return None;

      case ForceAndBridgeToObjC:
         return None;

      case AnyErasure:
      case BridgeToObjC:
      case BridgeFromObjC:
      case BridgeResultFromObjC:
         return Conversion::getBridging(getKind(), newSourceType,
                                        getBridgingResultType(),
                                        getBridgingLoweredResultType(),
                                        isBridgingExplicit());
   }
   llvm_unreachable("bad kind");
}

Optional<Conversion> Conversion::adjustForInitialForceValue() const {
   switch (getKind()) {
      case SubstToOrig:
      case OrigToSubst:
      case AnyErasure:
      case BridgeFromObjC:
      case BridgeResultFromObjC:
      case ForceAndBridgeToObjC:
         return None;

      case BridgeToObjC: {
         auto sourceOptType =
            OptionalType::get(getBridgingSourceType())->getCanonicalType();
         return Conversion::getBridging(ForceAndBridgeToObjC,
                                        sourceOptType,
                                        getBridgingResultType(),
                                        getBridgingLoweredResultType(),
                                        isBridgingExplicit());
      }
   }
   llvm_unreachable("bad kind");
}

void Conversion::dump() const {
   print(llvm::errs());
   llvm::errs() << '\n';
}

static void printReabstraction(const Conversion &conversion,
                               llvm::raw_ostream &out, StringRef name) {
   out << name << "(orig: ";
   conversion.getReabstractionOrigType().print(out);
   out << ", subst: ";
   conversion.getReabstractionSubstType().print(out);
   out << ')';
}

static void printBridging(const Conversion &conversion, llvm::raw_ostream &out,
                          StringRef name) {
   out << name << "(from: ";
   conversion.getBridgingSourceType().print(out);
   out << ", to: ";
   conversion.getBridgingResultType().print(out);
   out << ", explicit: " << conversion.isBridgingExplicit() << ')';
}

void Conversion::print(llvm::raw_ostream &out) const {
   switch (getKind()) {
      case SubstToOrig:
         return printReabstraction(*this, out, "SubstToOrig");
      case OrigToSubst:
         return printReabstraction(*this, out, "OrigToSubst");
      case AnyErasure:
         return printBridging(*this, out, "AnyErasure");
      case BridgeToObjC:
         return printBridging(*this, out, "BridgeToObjC");
      case ForceAndBridgeToObjC:
         return printBridging(*this, out, "ForceAndBridgeToObjC");
      case BridgeFromObjC:
         return printBridging(*this, out, "BridgeFromObjC");
      case BridgeResultFromObjC:
         return printBridging(*this, out, "BridgeResultFromObjC");
   }
   llvm_unreachable("bad kind");
}

static bool areRelatedTypesForBridgingPeephole(CanType sourceType,
                                               CanType resultType) {
   if (sourceType == resultType)
      return true;

   if (auto resultObjType = resultType.getOptionalObjectType()) {
      // Optional-to-optional.
      if (auto sourceObjType = sourceType.getOptionalObjectType()) {
         return areRelatedTypesForBridgingPeephole(sourceObjType, resultObjType);
      }

      // Optional injection.
      return areRelatedTypesForBridgingPeephole(sourceType, resultObjType);
   }

   // If the result type is AnyObject, then we can always apply the bridge
   // via Any.
   if (resultType->isAnyObject()) {
      // ... as long as the source type is not an Optional.
      if (sourceType->isBridgeableObjectType())
         return true;
   }

   // TODO: maybe other class existentials? Existential conversions?
   // They probably aren't important here.

   // All the other rules only apply to class types.
   if (!sourceType->mayHaveSuperclass() ||
       !resultType->mayHaveSuperclass())
      return false;

   // Walk up the class hierarchy looking for an exact match.
   while (auto superclass = sourceType->getSuperclass()) {
      sourceType = superclass->getCanonicalType();
      if (sourceType == resultType)
         return true;
   }

   // Otherwise, we don't know how to do this conversion.
   return false;
}

/// Does the given conversion turn a non-class type into Any, taking into
/// account optional-to-optional conversions?
static bool isValueToAnyConversion(CanType from, CanType to) {
   while (auto toObj = to.getOptionalObjectType()) {
      to = toObj;
      if (auto fromObj = from.getOptionalObjectType()) {
         from = fromObj;
      }
   }

   assert(to->isAny());

   // Types that we can easily transform into AnyObject:
   //   - classes and class-bounded archetypes
   //   - class existentials, even if not pure-@objc
   //   - @convention(objc) metatypes
   //   - @convention(block) functions
   return !from->isAnyClassReferenceType() &&
          !from->isBridgeableObjectType();
}

/// Check whether this conversion is Any??? to AnyObject???.  If the result
/// type is less optional, it doesn't count.
static bool isMatchedAnyToAnyObjectConversion(CanType from, CanType to) {
   while (auto fromObject = from.getOptionalObjectType()) {
      auto toObject = to.getOptionalObjectType();
      if (!toObject) return false;
      from = fromObject;
      to = toObject;
   }

   if (from->isAny()) {
      assert(to->lookThroughAllOptionalTypes()->isAnyObject());
      return true;
   }
   return false;
}

Optional<ConversionPeepholeHint>
lowering::canPeepholeConversions(PILGenFunction &SGF,
                                 const Conversion &outerConversion,
                                 const Conversion &innerConversion) {
   switch (outerConversion.getKind()) {
      case Conversion::OrigToSubst:
      case Conversion::SubstToOrig:
         // TODO: peephole these when the abstraction patterns are the same!
         return None;

      case Conversion::AnyErasure:
      case Conversion::BridgeFromObjC:
      case Conversion::BridgeResultFromObjC:
         // TODO: maybe peephole bridging through a Swift type?
         // This isn't actually something that happens in normal code generation.
         return None;

      case Conversion::ForceAndBridgeToObjC:
      case Conversion::BridgeToObjC:
         switch (innerConversion.getKind()) {
            case Conversion::AnyErasure:
            case Conversion::BridgeFromObjC:
            case Conversion::BridgeResultFromObjC: {
               bool outerExplicit = outerConversion.isBridgingExplicit();
               bool innerExplicit = innerConversion.isBridgingExplicit();

               // Never peephole if both conversions are explicit; there might be
               // something the user's trying to do which we don't understand.
               if (outerExplicit && innerExplicit)
                  return None;

               // Otherwise, we can peephole if we understand the resulting conversion
               // and applying the peephole doesn't change semantics.

               CanType sourceType = innerConversion.getBridgingSourceType();
               CanType intermediateType = innerConversion.getBridgingResultType();
               assert(intermediateType == outerConversion.getBridgingSourceType());

               // If we're doing a peephole involving a force, we want to propagate
               // the force to the source value.  If it's not in fact optional, that
               // won't work.
               bool forced =
                  outerConversion.getKind() == Conversion::ForceAndBridgeToObjC;
               if (forced) {
                  sourceType = sourceType.getOptionalObjectType();
                  if (!sourceType) return None;
                  intermediateType = intermediateType.getOptionalObjectType();
                  assert(intermediateType);
               }

               CanType resultType = outerConversion.getBridgingResultType();
               PILType loweredSourceTy = SGF.getLoweredType(sourceType);
               PILType loweredResultTy = outerConversion.getBridgingLoweredResultType();

               auto applyPeephole = [&](ConversionPeepholeHint::Kind kind) {
                  return ConversionPeepholeHint(kind, forced);
               };

               // Converting to Any doesn't do anything semantically special, so we
               // can apply the peephole unconditionally.
               if (isMatchedAnyToAnyObjectConversion(intermediateType, resultType)) {
                  if (loweredSourceTy == loweredResultTy) {
                     return applyPeephole(ConversionPeepholeHint::Identity);
                  } else if (isValueToAnyConversion(sourceType, intermediateType)) {
                     return applyPeephole(ConversionPeepholeHint::BridgeToAnyObject);
                  } else {
                     return applyPeephole(ConversionPeepholeHint::Subtype);
                  }
               }

               // Otherwise, undoing a bridging conversions can change semantics by
               // e.g. removing a copy, so we shouldn't do it unless the special
               // syntactic bridging peephole applies.  That requires one of the
               // conversions to be explicit.
               // TODO: use special PILGen to preserve semantics in this case,
               // e.g. by making a copy.
               if (!outerExplicit && !innerExplicit) {
                  return None;
               }

               // Okay, now we're in the domain of the bridging peephole: an
               // explicit bridging conversion can cancel out an implicit bridge
               // between related types.

               // If the source and destination types have exactly the same
               // representation, then (1) they're related and (2) we can directly
               // emit into the context.
               if (loweredSourceTy.getObjectType() == loweredResultTy.getObjectType()) {
                  return applyPeephole(ConversionPeepholeHint::Identity);
               }

               // Look for a subtype relationship between the source and destination.
               if (areRelatedTypesForBridgingPeephole(sourceType, resultType)) {
                  return applyPeephole(ConversionPeepholeHint::Subtype);
               }

               // If the inner conversion is a result conversion that removes
               // optionality, and the non-optional source type is a subtype of the
               // value type, this is just an implicit force.
               if (!forced &&
                   innerConversion.getKind() == Conversion::BridgeResultFromObjC) {
                  if (auto sourceValueType = sourceType.getOptionalObjectType()) {
                     if (!intermediateType.getOptionalObjectType() &&
                         areRelatedTypesForBridgingPeephole(sourceValueType, resultType)) {
                        forced = true;
                        return applyPeephole(ConversionPeepholeHint::Subtype);
                     }
                  }
               }

               return None;
            }

            default:
               return None;
         }
   }
   llvm_unreachable("bad kind");
}

ManagedValue
lowering::emitPeepholedConversions(PILGenFunction &SGF, PILLocation loc,
                                   const Conversion &outerConversion,
                                   const Conversion &innerConversion,
                                   ConversionPeepholeHint hint,
                                   SGFContext C,
                                   ValueProducerRef produceOrigValue) {
   auto produceValue = [&](SGFContext C) {
      if (!hint.isForced()) {
         return produceOrigValue(SGF, loc, C);
      }

      auto value = produceOrigValue(SGF, loc, SGFContext());
      auto &optTL = SGF.getTypeLowering(value.getType());
      // isForceUnwrap is hardcoded true because hint.isForced() is only
      // set by implicit force unwraps.
      return SGF.emitCheckedGetOptionalValueFrom(loc, value,
         /*isForceUnwrap*/ true,
                                                 optTL, C);
   };

   auto getBridgingSourceType = [&] {
      CanType sourceType = innerConversion.getBridgingSourceType();
      if (hint.isForced())
         sourceType = sourceType.getOptionalObjectType();
      return sourceType;
   };
   auto getBridgingResultType = [&] {
      return outerConversion.getBridgingResultType();
   };
   auto getBridgingLoweredResultType = [&] {
      return outerConversion.getBridgingLoweredResultType();
   };

   switch (hint.getKind()) {
      case ConversionPeepholeHint::Identity:
         return produceValue(C);

      case ConversionPeepholeHint::BridgeToAnyObject: {
         auto value = produceValue(SGFContext());
         return SGF.emitNativeToBridgedValue(loc, value, getBridgingSourceType(),
                                             getBridgingResultType(),
                                             getBridgingLoweredResultType(), C);
      }

      case ConversionPeepholeHint::Subtype: {
         // Otherwise, emit and convert.
         // TODO: if the context allows +0, use it in more situations.
         auto value = produceValue(SGFContext());
         PILType loweredResultTy = getBridgingLoweredResultType();

         // Nothing to do if the value already has the right representation.
         if (value.getType().getObjectType() == loweredResultTy.getObjectType())
            return value;

         CanType sourceType = getBridgingSourceType();
         CanType resultType = getBridgingResultType();
         return SGF.emitTransformedValue(loc, value, sourceType, resultType, C);
      }
   }
   llvm_unreachable("bad kind");
}
